#include <os/list.h>
#include <os/mm.h>
#include <os/lock.h>
#include <os/sched.h>
#include <os/time.h>
#include <os/irq.h>
#include <screen.h>
#include <stdio.h>
#include <assert.h>
#include <os/binsem.h>
#include <pgtable.h>
#include <user_programs.h>

#define SHELL_BEGIN 25

pcb_t pcb[NUM_MAX_TASK];
const ptr_t pid0_stack_k0 = INIT_KERNEL_STACK + PAGE_SIZE;
const ptr_t pid0_stack_k1 = INIT_KERNEL_STACK + 2*PAGE_SIZE;
pcb_t pid0_pcb = {
    .pid = 0,
    .kernel_sp = (ptr_t)pid0_stack_k0,
    .user_sp = (ptr_t)pid0_stack_k0,
    .preempt_count = 0,
    .status = TASK_RUNNING,
    .task_priority = 0,
    .priority = 0,
};

LIST_HEAD(ready_queue);
LIST_HEAD(sleep_queue);

/* current running task PCB */
pcb_t * volatile current_running;
pcb_t * volatile current_running_k0;
pcb_t * volatile current_running_k1;

/* global process id */
pid_t process_id = 0;

void do_scheduler(void)
{
    // TODO schedule
    // Modify the current_running pointer.
    pcb_t *next_running, *tmp;
    list_node_t *tmp_node;

    if(list_empty(&ready_queue))
        next_running = current_running;
    else{
        tmp_node = ready_queue.prev;
        next_running = list_entry(tmp_node, pcb_t, list);
    }

    tmp_node = ready_queue.next;
    while(tmp_node != &(ready_queue)){
        tmp = list_entry(tmp_node, pcb_t, list);
        if(tmp != &pid0_pcb)
            tmp->priority += 1;
        tmp_node = tmp_node->next;
    }
    next_running->priority = next_running->task_priority;
    if(current_running->status == TASK_RUNNING){
        current_running->status = TASK_READY;
        list_add_prior(&current_running->list);
    }
    list_del(&(next_running->list));
    next_running->status = TASK_RUNNING;
    current_running = next_running;
    
    set_satp(SATP_MODE_SV39, current_running->pid, (current_running->pgdir - 0xffffffc000000000) / 4096);
    local_flush_tlb_all();
    
    // restore the current_runnint's cursor_x and cursor_y
    vt100_move_cursor(current_running->cursor_x,
                      current_running->cursor_y);

}

void do_sleep(uint32_t sleep_time)
{
    // TODO: sleep(seconds)
    // note: you can assume: 1 second = `timebase` ticks
    // 1. block the current_running
    // 2. create a timer which calls `do_unblock` when timeout
    // 3. reschedule because the current_running is blocked.
    timer_create((TimerCallback)&do_unblock, &(current_running->list), (uint64_t)sleep_time*time_base);
    do_block(&current_running->list, &sleep_queue);
}

void do_block(list_node_t *pcb_node, list_head *queue)
{
    // TODO: block the pcb task into the block queue
    list_del(pcb_node);
    list_add_tail(pcb_node, queue);
    current_running->status = TASK_BLOCKED;
    do_scheduler();
}

void do_unblock(list_node_t *pcb_node)
{
    // TODO: unblock the `pcb` from the block queue
    pcb_t* tmp;
    list_del(pcb_node);
    list_add_prior(pcb_node);
    tmp = list_entry(pcb_node, pcb_t, list);
    tmp->status = TASK_READY;
}

 void list_add_prior(list_node_t *pcb_node){
    list_node_t *next;
    pcb_t *next_pcb, *tmp;
    tmp = list_entry(pcb_node, pcb_t, list);
    if(list_empty(&ready_queue)){
        list_add(pcb_node, &ready_queue);
        return ;
    }
    next = ready_queue.prev;
    next_pcb = list_entry(next, pcb_t, list);
    while(next_pcb->priority >= tmp->priority){
        if(next->prev != &(ready_queue))
          next = next->prev;
        else{
          list_add(pcb_node, &ready_queue);
          return ;
        }
        next_pcb = list_entry(next, pcb_t, list);
    }
    list_add(pcb_node, next);
}

void do_ps(void){
    int i = 1, j = 1;
    prints("[PROCESS TABLE]\n");
    if(pid0_pcb.status == TASK_RUNNING)
        prints("[0] PID: 0 STATUS: RUNNING\n");
    else if(pid0_pcb.status == TASK_READY)
        prints("[0] PID: 0 STATUS: READY\n");

    for(; j <= process_id; j++){
        if(pcb[j].status==TASK_RUNNING){
            prints("[%d] PID: %d STATUS: RUNNING\n",i,pcb[j].pid);
            i++;
        }
        else if(pcb[j].status==TASK_BLOCKED){
            prints("[%d] PID: %d STATUS: BLOCKED\n",i,pcb[j].pid);
            i++; 
        }
        else if(pcb[j].status==TASK_READY){
            prints("[%d] PID: %d STATUS: READY\n",i,pcb[j].pid);
            i++; 
        }
        else if(pcb[j].status==TASK_ZOMBIE){
            prints("[%d] PID: %d STATUS: ZOMBIE\n",i,pcb[j].pid);
            i++; 
        }
        else if(pcb[j].status==TASK_NONE);
    }
}

int do_kill(pid_t pid)
{
    if(pid > process_id || pcb[pid].status == TASK_NONE || pid == 0 || pid == 1)
        return 0;
    freePage(pcb[pid].kernel_sp - 1);
    free_process_page(pcb[pid].pgdir);
    pcb[pid].status = TASK_NONE;
    while(!list_empty(&(pcb[pid].wait_list))){
        list_node_t *tmp = pcb[pid].wait_list.next;
        do_unblock(tmp);
    }
    if(current_running->binsem_id != -1)
        op_binsem(current_running->binsem_id, BINSEM_OP_UNLOCK);
    list_del(&(pcb[pid].list));
    return 1;
}

void do_exit(void)
{
    if(current_running->mode == ENTER_ZOMBIE_ON_EXIT){
        current_running->status = TASK_ZOMBIE;
        while(!list_empty(&(current_running->wait_list))){
            list_node_t *tmp = current_running->wait_list.next;
            do_unblock(tmp);
        }
        if(current_running->binsem_id != -1)
            op_binsem(current_running->binsem_id, BINSEM_OP_UNLOCK);
        list_del(&(current_running->list));
    }
    else
        do_kill(current_running->pid);
    do_scheduler();
}

int do_waitpid(pid_t pid)
{
    if((pcb[pid].status != TASK_NONE) && (pcb[pid].status != TASK_ZOMBIE))
        do_block(&(current_running->list), &(pcb[pid].wait_list));
    return 1;
}

pid_t do_getpid()
{
    return current_running->pid;
}

void do_show_exec(){
    int i;
    for (i = 0; i < 4; i++){
        prints("%s\n", elf_files[i].file_name);
    }
}